#include "LibraryFunctions.h"

LibraryFunctions::LibraryFunctions(int diskSizeInBytes, int dataBlockSize,
		int numberOfInodes): systemCallLayer (diskSizeInBytes, dataBlockSize, numberOfInodes){
	//TODO remove this line later
	//systemCallLayer.test();
	processesMap.clear();
	currentPath = new string ("/");
	shouldExit = false;
}

LibraryFunctions::LibraryFunctions(): systemCallLayer() {
	//TODO remove this line later
	//systemCallLayer.test1();
	processesMap.clear();
	currentPath = new string ("/");
	shouldExit = false;
}

LibraryFunctions::~LibraryFunctions() {}

void LibraryFunctions::run() {
	string keyBoardInput;
	string delimChar = " \t\n";

	while (!shouldExit) {
		vector<string> stringVector;
		cout << ">";
		getline (cin, keyBoardInput);
		parseString(keyBoardInput, delimChar, stringVector);
		if (stringVector.size() > 0) {
			int commandResult = processCommand(stringVector);
			cout << commandResult << endl;
		}
	}
}

int LibraryFunctions::parseString(string sStringToParse, string delimChar,
		vector<string>& rReturnStringArray) {

 const char* str = sStringToParse.c_str();
 const char* seps = delimChar.c_str();
 char* token = NULL;
 int i = 0;

   /* Establish string and get the first token: */
   token =  strtok( (char*)str, seps );

   while( token != NULL ) {

      /* While there are tokens in "string" */

     rReturnStringArray.push_back( string(token));

     i++;

      /* Get next token: */
      token = strtok( NULL, seps );
   }

 return i;
}

int LibraryFunctions::processCommand (vector<string>& userInput) {
	if (userInput.size() != 0) {
		string command = userInput.front();
		//cout << "command: " << command << endl;
		if (command.compare("mkdir") == 0 && userInput.size() == 2) {
			return mkdir(userInput.at(1).c_str());
		} else if (command.compare("create") == 0 && userInput.size() == 3) {
			return create (userInput.at(1).c_str(), userInput.at(2));
		} else if (command.compare("hdlink") == 0 && userInput.size() == 3) {
			return hdlink(userInput.at(1).c_str(), userInput.at(2).c_str());
		} else if (command.compare("sflink") == 0 && userInput.size() == 3) {
			return sflink(userInput.at(1).c_str(), userInput.at(2).c_str());
		} else if (command.compare("open") == 0 && userInput.size() == 3) {
			return open(userInput.at(1).c_str(), userInput.at(2));
		} else if (command.compare("close") == 0 && userInput.size() == 2) {
			return close(atoi (userInput.at(1).c_str()));
		} else if (command.compare("lck_rd") == 0 && userInput.size() == 2) {
			return lck_rd(atoi (userInput.at(1).c_str()));
		} else if (command.compare("lck_wr") == 0 && userInput.size() == 2) {
			return lck_wr(atoi (userInput.at(1).c_str()));
		} else if (command.compare("rlslck_rd") == 0 && userInput.size() == 2) {
			return rlslck_rd(atoi (userInput.at(1).c_str()));
		} else if (command.compare("rlslck_wr") == 0 && userInput.size() == 2) {
			return rlslck_wr(atoi (userInput.at(1).c_str()));
		} else if (command.compare("read") == 0 &&  userInput.size() == 3) {
			return read(atoi (userInput.at(1).c_str()), atoi (userInput.at(2).c_str()));
		} else if (command.compare("write") == 0 && userInput.size() == 3) {
			return write(atoi (userInput.at(1).c_str()), userInput.at(2).c_str());
		} else if (command.compare("writeFile") == 0 && userInput.size() == 3) {
			return writeFile(atoi (userInput.at(1).c_str()), userInput.at(2).c_str());
		} else if (command.compare("cd") == 0 && userInput.size() == 2) {
			return cd(userInput.at(1).c_str());
		} else if (command.compare("ls") == 0 && userInput.size() <= 2) {
			//no arguments mean list the current path
			if (userInput.size() == 1) {
				string *path = currentPath;
				return ls((*path).c_str());
			} else {
				return ls(userInput.at(1).c_str());
			}
		} else if (command.compare("crprc") == 0 && userInput.size() == 3) {
			return crprc(atoi(userInput.at(1).c_str()),atoi(userInput.at(2).c_str()));
		} else if (command.compare("swprc") == 0 && userInput.size() == 2) {
			return swprc(atoi(userInput.at(1).c_str()));
		} else if (command.compare("batch") == 0 && userInput.size() == 2) {
			return batch(userInput.at(1).c_str());
		} else if (command.compare("writeFile") == 0 && userInput.size() == 3) {
			return writeFile(atoi(userInput.at(1).c_str()),userInput.at(2).c_str());
		} else if (command.compare("lseek") == 0 && userInput.size() == 3) {
			return lseek(atoi(userInput.at(1).c_str()),atoi(userInput.at(2).c_str()));
		} else if (command.compare("rm") == 0 && userInput.size() == 2) {
			return rm(userInput.at(1).c_str());
		} else if (command.compare("mv") == 0 && userInput.size() == 3) {
			return mv(userInput.at(1).c_str(), userInput.at(2).c_str());
		} else if (command.compare("rmdir") == 0 && userInput.size() == 2) {
			return rmdir(userInput.at(1).c_str());
		} else if (command.compare("rmdir_r") == 0 && userInput.size() == 2) {
			return rmdir_r(userInput.at(1).c_str());
		} else if (command.compare("exit") == 0 && userInput.size() == 1) {
			shouldExit = true;
			return 0;
		} else {
			cout << "invalid command" << endl;
			return -1;
		}
	}
	return -1;
}

int LibraryFunctions::mkdir (const char* dirName) {
	if (strcmp(dirName,"/") == 0){
		cerr << "not a valid directory name " << dirName << endl;
		return -1;
	}
	string absolutPath = makeAbsolutePath(clearPath (dirName), countNumOfDirsBack(clearPath (dirName)));
	if (absolutPath.compare("") == 0) {
		return -1;
	} else {
		return systemCallLayer.MakeDir(absolutPath.c_str());
	}
}

//flags can be one of the following: "read-only" or "read-and-write"
int LibraryFunctions::create (const char* fileName, string flags) {
	if (strcmp(fileName,".") == 0 || strcmp(fileName,"..") == 0 || strcmp(fileName,"/") == 0){
		cerr << "not a valid file name " << fileName << endl;
		return -1;
	}
	if (flags.compare("read-only") != 0 && flags.compare("read-and-write") != 0) {
		cerr << flags << " is not a valid flag for the creation of "<< fileName << endl;
		return -1;
	}

	string absolutePath = makeAbsolutePath(clearPath(fileName),countNumOfDirsBack(clearPath(fileName)));
	if (absolutePath.compare("") == 0) {
		return -1;
	} else {
		return systemCallLayer.MakeFile(absolutePath.c_str(),"regular",flags);
	}
}

int LibraryFunctions::hdlink (const char* target, const char* newFileName) {
	if (strcmp(newFileName,".") == 0 || strcmp(newFileName,"..") == 0 || strcmp(newFileName,"/") == 0){
		cerr << "not a valid file name " << newFileName << endl;
			return -1;
	} else {
		string targetAbsolutePath = makeAbsolutePath(clearPath(target)
				,countNumOfDirsBack(clearPath(target)));
		string newFileAbsolutePath = makeAbsolutePath(clearPath(newFileName)
				,countNumOfDirsBack(clearPath(newFileName)));
		if (targetAbsolutePath.compare("") == 0 || newFileAbsolutePath.compare("") == 0) {
			return -1;
		}
		char param1[targetAbsolutePath.size()];
		char param2[newFileAbsolutePath.size()];
		strcpy(param1,targetAbsolutePath.c_str());
		strcpy(param2,newFileAbsolutePath.c_str());
		return systemCallLayer.MakeHLink(param1,param2);
	}
}

int LibraryFunctions::sflink (const char* target, const char* newFileName) {
	if (strcmp(target,".") == 0 || strcmp(target,"..") == 0 || strcmp(target,"/") == 0){
		cerr << "not a valid file name " << target << endl;
		return -1;
	}
	if (strcmp(newFileName,".") == 0 || strcmp(newFileName,"..") == 0 || strcmp(newFileName,"/") == 0){
		cerr << "not a valid file name " << newFileName << endl;
		return -1;
	}

	string targetFileAbsolutePath = makeAbsolutePath(clearPath (target),
			countNumOfDirsBack(clearPath (target)));
	string newFileAbsolutePath = makeAbsolutePath(clearPath (newFileName),
			countNumOfDirsBack(clearPath (newFileName)));

	if (targetFileAbsolutePath.compare("") == 0 || newFileAbsolutePath.compare("") == 0) {
		return -1;
	} else {
		int fd = systemCallLayer.MakeFile(newFileAbsolutePath.c_str(), "soft-link", "read-and-write");
		write(fd, targetFileAbsolutePath.c_str());
		close (fd);
		return 0;
	}
}

int LibraryFunctions::open (const char* fileName, string flags) {
	if (strcmp(fileName,".") == 0 || strcmp(fileName,"..") == 0 || strcmp(fileName,"/") == 0){
		cerr << "not a valid file name " << fileName << endl;
		return -1;
	}
	if (flags.compare("read-only") != 0 && flags.compare("read-and-write") != 0) {
		cerr << flags << " is not a valid flag for the creation of "<< fileName << endl;
		return -1;
	}

	string absolutPath = makeAbsolutePath(clearPath (fileName), countNumOfDirsBack(clearPath (fileName)));
	if (absolutPath.compare("") == 0) {
		return -1;
	} else {
		return systemCallLayer.Open(absolutPath.c_str(), flags);
	}
}

int LibraryFunctions::close (int fd) {
	return systemCallLayer.Close(fd);
}

int LibraryFunctions::read (int fd, int numBytes) {
	unsigned char buf[numBytes];
	int bytesRead = systemCallLayer.Read(fd, numBytes, buf);
	if (bytesRead != -1) {
		 cout << buf << "\n" << bytesRead << endl;
		 return 0;
	} else {
		cerr << "Can't read from fd " << fd << endl;
		return -1;
	}
}

int LibraryFunctions::write (int fd, const char* dataString) {
	return systemCallLayer.Write(fd, strlen(dataString), (unsigned char*)dataString);
}

int LibraryFunctions::writeFile (int fd, const char* fileName) {
	fstream realFile;
	string line;
	int offset = 0;
	realFile.open(fileName, ios::in);
	if (realFile.is_open()) {
		while (!realFile.eof()) {
			getline(realFile,line);
			line += "\n";
			lseek (fd, offset);
			write(fd, line.c_str());
			offset += line.size();
		}
		realFile.close();
		return 0;
	} else {
		cerr << "Can't open real file " << fileName << endl;
		return -1;
	}
	return 0;
}

int LibraryFunctions::lck_rd (int fd) {
	return systemCallLayer.lockRead(fd);
}

int LibraryFunctions::lck_wr (int fd) {
	return systemCallLayer.lockWrite(fd);
}

int LibraryFunctions::rlslck_rd (int fd) {
	return systemCallLayer.releaseLockRead(fd);
}

int LibraryFunctions::rlslck_wr (int fd) {
	return systemCallLayer.releaseLockWrite(fd);
}

int LibraryFunctions::lseek (int fd, int offset) {
	return systemCallLayer.Seek(fd, offset);
}

int LibraryFunctions::rm (const char* fileName) {
	if (strcmp(fileName,".") == 0 || strcmp(fileName,"..") == 0 || strcmp(fileName,"/") == 0){
		cerr << "not a valid file name " << fileName << endl;
		return -1;
	}

	string absolutePath = makeAbsolutePath(clearPath (fileName), countNumOfDirsBack(clearPath (fileName)));
	if (absolutePath.compare("") == 0) {
		return -1;
	} else {
		return systemCallLayer.RmFile(absolutePath.c_str());
	}
}

int LibraryFunctions::rmdir (const char* dirName) {
	if (strcmp(dirName,".") == 0 || strcmp(dirName,"..") == 0 || strcmp(dirName,"/") == 0){
		cerr << "not a valid file name " << dirName << endl;
		return -1;
	}
	string absolutePath = makeAbsolutePath(clearPath (dirName), countNumOfDirsBack(clearPath (dirName)));
	if (absolutePath.compare("") == 0) {
		return -1;
	} else {
		return systemCallLayer.RmDir(absolutePath.c_str());
	}
}

int LibraryFunctions::rmdir_r (const char* dirName) {
	if (strcmp(dirName,".") == 0 || strcmp(dirName,"..") == 0 || strcmp(dirName,"/") == 0){
		cerr << "not a valid file name " << dirName << endl;
		return -1;
	}
	string absolutePath = makeAbsolutePath(clearPath (dirName), countNumOfDirsBack(clearPath (dirName)));
	if (absolutePath.compare("") == 0) {
		return -1;
	} else {
		return systemCallLayer.resetAndRemoveDir(absolutePath.c_str());
	}
}

int LibraryFunctions::ls (const char* dirName) {
	string absolutePath = makeAbsolutePathForCdAndLs(clearPath(dirName), countNumOfDirsBackForCd(clearPath(dirName)));
	if (absolutePath.compare("") == 0) {
		return -1;
	}
	char directoryName[strlen(dirName)];
	strcpy(directoryName, dirName);
	int sizeOfDir = systemCallLayer.getNumOfFileInDir(directoryName);
	char buf[sizeOfDir*20];
	systemCallLayer.ls(directoryName,buf);
	cout << buf << endl;
	return 0;
}

int LibraryFunctions::mv (const char* oldfileName, const char* newFileName) {
	if (strcmp(oldfileName,".") == 0 || strcmp(oldfileName,"..") == 0 || strcmp(oldfileName,"/") == 0){
		cerr << "not a valid file name " << oldfileName << endl;
		return -1;
	}
	if (strcmp(newFileName,".") == 0 || strcmp(newFileName,"..") == 0 || strcmp(newFileName,"/") == 0){
		cerr << "not a valid file name " << newFileName << endl;
		return -1;
	}

	string oldFileAbsolutePath = makeAbsolutePath(clearPath (oldfileName),
			countNumOfDirsBack(clearPath (oldfileName)));
	string newFileAbsolutePath = makeAbsolutePath(clearPath (newFileName),
			countNumOfDirsBack(clearPath (newFileName)));

	if (oldFileAbsolutePath.compare("") == 0 || newFileAbsolutePath.compare("") == 0) {
		return -1;
	} else {
		return systemCallLayer.moveFile(oldFileAbsolutePath.c_str(), newFileAbsolutePath.c_str());
	}
}


int LibraryFunctions::cd (const char* newDir) {
	string absolutePath = makeAbsolutePathForCdAndLs(clearPath(newDir), countNumOfDirsBackForCd(clearPath(newDir)));
	if (absolutePath.compare("") == 0) {
		return -1;
	}

	//for soft-links to dir:
	string targetDir;
	systemCallLayer.findDir(absolutePath.c_str(), targetDir);
	if (targetDir.size() > 0) {
		*currentPath = targetDir;
		cout << "Entering to: " << *currentPath << endl;
		return 0;
	}
	cout << "directory " << newDir << " doesn't exists or this is a broken soft-link" << endl;
	return -1;
}

int LibraryFunctions::swprc (int newProcessNum) {
	map<int,ProcessInfo*>::iterator searchedProcess = processesMap.find(newProcessNum);
	if (searchedProcess == processesMap.end()) {
		cerr << "Process with ID " << newProcessNum << " doesn't exist!" << endl;
		return -1;
	} else {
		systemCallLayer.contextSwitch(newProcessNum, searchedProcess->second->getProcessMap());
		currentPath = searchedProcess->second->getCurrPath();
		return 0;
	}
}

int LibraryFunctions::crprc (int id, int parent) {
	map<int,ProcessInfo*>::iterator searchedProcess = processesMap.find(id);
	//there exists no previous process with the same id
	if (searchedProcess == processesMap.end()) {
		//this process is created with no open files
		if (parent == -1) {
			map <int,FileDescEntry*>* processMap = new map <int,FileDescEntry*>();
			ProcessInfo* pInfo = new ProcessInfo(processMap, new string ("/"));
			processesMap.insert(pair<int,ProcessInfo*> (id, pInfo));
			return 0;
		//the parent process exists
		} else if ((searchedProcess = processesMap.find(parent)) != processesMap.end()) {
			ProcessInfo* pInfo = new ProcessInfo(*(searchedProcess->second));
			map <int,FileDescEntry*>* processMap = pInfo->getProcessMap();
			map <int,FileDescEntry*>::iterator processMapIterator = processMap->begin();
			while (processMapIterator != processMap->end()) {
				systemCallLayer.incOpenTableConnectionsCounter
					(processMapIterator->second->getPointerToInode());
				processMapIterator++;
			}

			processesMap.insert(pair<int,ProcessInfo*> (id, pInfo));
			return 0;
			//the parent process doesn't exist
		} else {
			cerr << "The parent process " << parent << " doesn't exist!" << endl;
			return -1;
		}

	} else {
		cerr << "A process with ID " << id << " already exists!" << endl;
		return -1;
	}
}

int LibraryFunctions::batch (const char* fileName) {
	fstream batchFile;
	string line;
	string delimChar = " ";
	batchFile.open(fileName, ios::in);
	if (batchFile.is_open()) {
		vector<string> parsedLine;
		while (!batchFile.eof()) {
			parsedLine.clear();
			getline(batchFile,line);
			parseString(line,delimChar,parsedLine);
			processCommand (parsedLine);
		}
		batchFile.close();
		return 0;
	} else {
		cerr << "Can't open batch file " << fileName << endl;
		return -1;
	}
}

bool LibraryFunctions::isValidDirPath(const char* path) {
	bool valid = true;
	if (strlen (path) == 2 && path[0] == '.' && path[1] == '/') {
		valid = false;
	}

	if (strlen (path) == 3 && path[0] == '.' && path[1] == '.' && path[2] == '/') {
		valid = false;
	}

	if (path[0] == '.' && path[1] == '/') {
		valid = false;
	}

	int i=0;
	const char dot[] = "."; const char slash[] = "/";
	while (valid && path[i]) {
		if (path[i]==dot[0] || path[i]==slash[0]) {
			valid = false;
		}
		i++;
	}
	return valid;
}

string LibraryFunctions::makeAbsolutePath (const char* path, int numOfDirsBack) {
	if (path[0] == '/') {
		//the path is absolute
		return path;
	}
	string pathCopy = path;
	const char* cPathCopy = pathCopy.c_str();
	/*for (int i=0; i<(int)strlen(path); i++) {
		pathCopy += path[i];
	}*/

	//int numOfDirsBack = countNumOfDirsBack(path);
	if (numOfDirsBack > 0) {
	//if the path is relative and starts from some prev. dir, like "../../DirA/a.txt"
		vector<string> currentDirsVector = parsePath (*currentPath);
		if ((int)currentDirsVector.size() < numOfDirsBack) {
			cerr << "The path " << path << " doesn't exist" << endl;
			return "";
		} else {
			//builds the wanted absolute path:
			string absPath = "";
			for (int j=0; j<((int)currentDirsVector.size() - numOfDirsBack); j++) {
				absPath += "/" + currentDirsVector[j];
			}
			//parse the path to find the specific file / dir without the path:
			vector<string> pathDirsVector = parsePath (cPathCopy);
			/*
			for (int i=0; i<(int)pathDirsVector.size(); i++) {
				cout << pathDirsVector.at(i) << endl;
			}
			*/

			if (pathDirsVector.size() == 0) {
				return absPath + "/";
			}
			return absPath + "/" + pathDirsVector[pathDirsVector.size()-1] ;
		}
	}

	//finally, if the path starts from the current directory, like "DirA/a.txt"
	if ((*currentPath).compare("/") == 0) {
		return *currentPath + path;
	} else {
		return *currentPath + "/" + path;
	}
}


string LibraryFunctions::makeAbsolutePathForCdAndLs(const char* path, int numOfDirsBack) {
	if (strlen(path) >= 1 && path[0] == '/') {
		//the path is absolute
		return path;
	}
	if (strlen(path) == 1 && path[0] == '.') {
		//the path is absolute
		return *currentPath;
	}

	string pathCopy = path;
	const char* cPathCopy = pathCopy.c_str();

	//int numOfDirsBack = countNumOfDirsBack(path);
	if (numOfDirsBack > 0) {
	//if the path is relative and starts from some prev. dir, like "../../DirA/a.txt"
		vector<string> currentDirsVector = parsePath (*currentPath);
		if ((int)currentDirsVector.size() < numOfDirsBack) {
			cerr << "The path " << path << " doesn't exist" << endl;
			return "";
		} else {
			//builds the wanted absolute path:
			string absPath = "/";
			for (int j=0; j<((int)currentDirsVector.size() - numOfDirsBack); j++) {
				absPath += currentDirsVector[j] + "/";
			}
			//parse the path to find the specific file / dir without the path:
			vector<string> pathDirsVector = parsePath (cPathCopy);
			/*
			for (int i=0; i<(int)pathDirsVector.size(); i++) {
				cout << pathDirsVector.at(i) << endl;
			}
			*/

			if (pathDirsVector.size() == 0) {
				return absPath;
			} else {
				string ans = absPath;
				for (int i=0; i<(int)pathDirsVector.size(); i++) {
					if (pathDirsVector.at(i).compare("..")!=0) {
						ans += pathDirsVector.at(i) + "/";
					}
				}
				return ans;
			}
		}
	}

	//finally, if the path starts from the current directory, like "DirA/a.txt"
	if ((*currentPath).compare("/") == 0) {
		return *currentPath + path;
	} else {
		return *currentPath + "/" + path;
	}
}


//TODO Refactor - This function copied from SystemCallLayer
vector<string> LibraryFunctions::parsePath(string sStringToParse) {
	vector<string> parsedPath;
	parsedPath.clear();
	const char* str = sStringToParse.c_str();
	const char* seps = "/";
	char* token = NULL;
	int i = 0;

	/* Establish string and get the first token: */
	token =  strtok( (char*)str, seps );
	while( token != NULL ) {
		/* While there are tokens in "string" */
		parsedPath.push_back(token);
		i++;
		/* Get next token: */
		token = strtok( NULL, seps );
	}
	return parsedPath;
}

const char* LibraryFunctions::clearPath (const char* name) {
	string newName = name;
	if (name[0] == '.' && name[1] == '/') {
		newName = "";
		for (int i=2; i<(int)strlen(name); i++) {
			newName += name[i];
		}
	}
	return newName.c_str();
}

int LibraryFunctions::countNumOfDirsBack (const char* path) {
	int numOfDirsBack = 0;
	int i=0;
	while ((int)strlen(path) > i+2 &&
			(path[i] == '.' && path[i+1] == '.' && path[i+2]=='/')) {
		numOfDirsBack++;
		i+=3;
	}
	return numOfDirsBack;
}

int LibraryFunctions::countNumOfDirsBackForCd (const char* path) {
	int numOfDirsBack = 0;
	int i=0;
	if (strlen(path) == 2 && path[i] == '.' && path[i+1] == '.') {
		return 1;
	}
	while ((int)strlen(path) > i+2 &&
			(path[i] == '.' && path[i+1] == '.' && path[i+2]=='/')) {
		numOfDirsBack++;
		i+=3;
	}
	return numOfDirsBack;
}

void LibraryFunctions::removeDotDotAndSlashFromEnd(string& absolutePath) {
	if (absolutePath.size() > 1 && absolutePath[absolutePath.size()-1]=='.'
								&& absolutePath[absolutePath.size()-2]=='.') {
		absolutePath = absolutePath.substr(0,absolutePath.size()-2);
	}
	if (absolutePath.size() > 1 && absolutePath.at(absolutePath.size()-1) == '/') {
		absolutePath = absolutePath.substr(0,absolutePath.size()-1);
	}
}


//////////////////////////////////////////// TESTING \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

void LibraryFunctions::test() {
	//currentPath = "/barak/bawaki/agivson/bawakizuna";
	currentPath = new string ("/");

	const char name1[] = "/barak";
	const char name2[] = "/barak/bawaki";
	const char name3[] = "/barak/bawaki/agivson";
	const char name4[] = "/barak/bawaki/agivson/bawakizuna";
	const char name5[] = "../..u///";
	cout << "The current path is " << currentPath << endl;
	cout << "The cleared path of " << name5 << " is " << clearPath(name5) << endl;
	//cout << "The absolute path of " << clearPath(name5) << " is " << makeAbsolutePath (clearPath(name5)) << endl;


	const char name[] = "sdgsg/dsgsg";
	const char namee[] = "sdgsgdsgsg.";
	if (isValidDirPath(name) || isValidDirPath(namee)) {
		cout << "OK!!!"<< endl;
	}

	cout << mkdir(name1) << endl;

}
